{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "Zero_shot_learning.ipynb",
      "provenance": [],
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "accelerator": "GPU"
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/PacktPublishing/Modern-Computer-Vision-with-PyTorch/blob/master/Chapter14/Zero_shot_learning.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "-q5sBEc5EIpI",
        "outputId": "4d91014a-9266-4dfe-8a4b-07f90a931d8e",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 187
        }
      },
      "source": [
        "!git clone https://github.com/sizhky/zero-shot-learning/\n",
        "!pip install -Uq torch_snippets\n",
        "%cd zero-shot-learning/src\n",
        "import gzip\n",
        "import _pickle as cPickle\n",
        "from torch_snippets import *\n",
        "from sklearn.preprocessing import LabelEncoder, normalize\n",
        "device = 'cuda' if torch.cuda.is_available() else 'cpu'"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Cloning into 'zero-shot-learning'...\n",
            "remote: Enumerating objects: 102, done.\u001b[K\n",
            "remote: Total 102 (delta 0), reused 0 (delta 0), pack-reused 102\u001b[K\n",
            "Receiving objects: 100% (102/102), 134.47 MiB | 36.40 MiB/s, done.\n",
            "Resolving deltas: 100% (45/45), done.\n",
            "\u001b[K     |████████████████████████████████| 36.7MB 87kB/s \n",
            "\u001b[K     |████████████████████████████████| 61kB 7.8MB/s \n",
            "\u001b[K     |████████████████████████████████| 102kB 9.4MB/s \n",
            "\u001b[?25h  Building wheel for contextvars (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
            "/content/zero-shot-learning/src\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "S33kWM_TEeC0"
      },
      "source": [
        "WORD2VECPATH = \"../data/class_vectors.npy\"\n",
        "DATAPATH = \"../data/zeroshot_data.pkl\""
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "kULYCEK7Ezip"
      },
      "source": [
        "with open('train_classes.txt', 'r') as infile:\n",
        "    train_classes = [str.strip(line) for line in infile]"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "iomjeHmyE1xi"
      },
      "source": [
        "with gzip.GzipFile(DATAPATH, 'rb') as infile:\n",
        "    data = cPickle.load(infile)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "7qZBBK9jE3Sg"
      },
      "source": [
        "training_data = [instance for instance in data if instance[0] in train_classes]\n",
        "zero_shot_data = [instance for instance in data if instance[0] not in train_classes]\n",
        "np.random.shuffle(training_data)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "_ZAv4KiaE420"
      },
      "source": [
        "train_size = 300 # per class\n",
        "train_data, valid_data = [], []\n",
        "for class_label in train_classes:\n",
        "    ctr = 0\n",
        "    for instance in training_data:\n",
        "        if instance[0] == class_label:\n",
        "            if ctr < train_size:\n",
        "                train_data.append(instance)\n",
        "                ctr+=1\n",
        "            else:\n",
        "                valid_data.append(instance)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "tYjuW30RE6fx"
      },
      "source": [
        "np.random.shuffle(train_data)\n",
        "np.random.shuffle(valid_data)\n",
        "vectors = {i:j for i,j in np.load(WORD2VECPATH, allow_pickle=True)}"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "fWkDpklSE79O"
      },
      "source": [
        "train_data = [(feat, vectors[clss]) for clss,feat in train_data]\n",
        "valid_data = [(feat, vectors[clss]) for clss,feat in valid_data]"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "haQky_e2E9SL"
      },
      "source": [
        "train_clss = [clss for clss,feat in train_data]\n",
        "valid_clss = [clss for clss,feat in valid_data]\n",
        "zero_shot_clss = [clss for clss,feat in zero_shot_data]"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "VAmZUA_LE-pu"
      },
      "source": [
        "x_train, y_train = zip(*train_data)\n",
        "x_train, y_train = np.squeeze(np.asarray(x_train)), np.squeeze(np.asarray(y_train))\n",
        "x_train = normalize(x_train, norm='l2')\n",
        "\n",
        "x_valid, y_valid = zip(*valid_data)\n",
        "x_valid, y_valid = np.squeeze(np.asarray(x_valid)), np.squeeze(np.asarray(y_valid))\n",
        "x_valid = normalize(x_valid, norm='l2')\n",
        "\n",
        "y_zsl, x_zsl = zip(*zero_shot_data)\n",
        "x_zsl, y_zsl = np.squeeze(np.asarray(x_zsl)), np.squeeze(np.asarray(y_zsl))\n",
        "x_zsl = normalize(x_zsl, norm='l2')"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "6HCmIEmhFAiD"
      },
      "source": [
        "from torch.utils.data import TensorDataset\n",
        "\n",
        "trn_ds = TensorDataset(*[torch.Tensor(t).to(device) for t in [x_train, y_train]])\n",
        "val_ds = TensorDataset(*[torch.Tensor(t).to(device) for t in [x_valid, y_valid]])\n",
        "\n",
        "trn_dl = DataLoader(trn_ds, batch_size=32, shuffle=True)\n",
        "val_dl = DataLoader(val_ds, batch_size=32, shuffle=False)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "LJod2CHNFCGF"
      },
      "source": [
        "def build_model(): \n",
        "    return nn.Sequential(\n",
        "        nn.Linear(4096, 1024), nn.ReLU(inplace=True),\n",
        "        nn.BatchNorm1d(1024), nn.Dropout(0.8),\n",
        "        nn.Linear(1024, 512), nn.ReLU(inplace=True),\n",
        "        nn.BatchNorm1d(512), nn.Dropout(0.8),\n",
        "        nn.Linear(512, 256), nn.ReLU(inplace=True),\n",
        "        nn.BatchNorm1d(256), nn.Dropout(0.8),\n",
        "        nn.Linear(256, 300)\n",
        "    )"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "0h1dNnFSFM1O"
      },
      "source": [
        "def train_batch(model, data, optimizer, criterion):\n",
        "    ims, labels = data\n",
        "    _preds = model(ims)\n",
        "    optimizer.zero_grad()\n",
        "    loss = criterion(_preds, labels)\n",
        "    loss.backward()\n",
        "    optimizer.step()\n",
        "    return loss.item()\n",
        "\n",
        "@torch.no_grad()\n",
        "def validate_batch(model, data, criterion):\n",
        "    ims, labels = data\n",
        "    _preds = model(ims)\n",
        "    loss = criterion(_preds, labels)\n",
        "    return loss.item()"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "XF-YyXTXFOut",
        "outputId": "ffbb890b-551a-45a0-efdb-f1e10a53ae6a",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 490
        }
      },
      "source": [
        "model = build_model().to(device)\n",
        "criterion = nn.MSELoss()\n",
        "optimizer = optim.Adam(model.parameters(), lr=1e-3)\n",
        "n_epochs = 60\n",
        "\n",
        "log = Report(n_epochs)\n",
        "for ex in range(n_epochs):\n",
        "    N = len(trn_dl)\n",
        "    for bx, data in enumerate(trn_dl):\n",
        "        loss = train_batch(model, data, optimizer, criterion)\n",
        "        log.record(ex+(bx+1)/N, trn_loss=loss, end='\\r')\n",
        "\n",
        "    N = len(val_dl)\n",
        "    for bx, data in enumerate(val_dl):\n",
        "        loss = validate_batch(model, data, criterion)\n",
        "        log.record(ex+(bx+1)/N, val_loss=loss, end='\\r')\n",
        "        \n",
        "    if ex == 10: optimizer = optim.Adam(model.parameters(), lr=1e-4)\n",
        "    if ex == 40: optimizer = optim.Adam(model.parameters(), lr=1e-5)\n",
        "    if not (ex+1)%10: log.report_avgs(ex+1)\n",
        "\n",
        "log.plot(log=True)"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "EPOCH: 10.000\ttrn_loss: 0.019\tval_loss: 0.019\t(122.92s - 614.58s remaining)\n",
            "EPOCH: 20.000\ttrn_loss: 0.014\tval_loss: 0.014\t(237.73s - 475.46s remaining)\n",
            "EPOCH: 30.000\ttrn_loss: 0.013\tval_loss: 0.013\t(359.53s - 359.53s remaining)\n",
            "EPOCH: 40.000\ttrn_loss: 0.012\tval_loss: 0.013\t(479.74s - 239.87s remaining)\n",
            "EPOCH: 50.000\ttrn_loss: 0.011\tval_loss: 0.013\t(592.00s - 118.40s remaining)\n",
            "EPOCH: 60.000\ttrn_loss: 0.011\tval_loss: 0.013\t(704.25s - 0.00s remaining)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "<Figure size 576x432 with 1 Axes>"
            ]
          },
          "metadata": {
            "tags": [],
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "U39Fmo0VFQ6C"
      },
      "source": [
        "pred_zsl = model(torch.Tensor(x_zsl).to(device)).cpu().detach().numpy()\n",
        "\n",
        "class_vectors = sorted(np.load(WORD2VECPATH, allow_pickle=True), key=lambda x: x[0])\n",
        "classnames, vectors = zip(*class_vectors)\n",
        "classnames = list(classnames)\n",
        "\n",
        "vectors = np.array(vectors)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "PPKdmdl7HHat",
        "outputId": "f9a0fe71-e7f9-445b-effd-839d5dbfa1ef",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        }
      },
      "source": [
        "dists = (pred_zsl[None] - vectors[:,None])\n",
        "dists = (dists**2).sum(-1).T\n",
        "\n",
        "best_classes = []\n",
        "for item in dists:\n",
        "    best_classes.append([classnames[j] for j in np.argsort(item)[:5]])\n",
        "\n",
        "np.mean([i in J for i,J in zip(zero_shot_clss, best_classes)])"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "0.7248624312156078"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 16
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "88CMg_0XIjMZ"
      },
      "source": [
        ""
      ],
      "execution_count": null,
      "outputs": []
    }
  ]
}